/*
 * Copyright (c) 2009, PostgreSQL Global Development Group
 * See the LICENSE file in the project root for more information.
 */

package org.postgresql.xa;

import org.checkerframework.checker.nullness.qual.Nullable;

import java.util.Arrays;
import java.util.Base64;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;

import javax.transaction.xa.Xid;

class RecoveredXid implements Xid {
  int formatId;
  byte[] globalTransactionId;
  byte[] branchQualifier;

  RecoveredXid(int formatId, byte[] globalTransactionId, byte[] branchQualifier) {
    this.formatId = formatId;
    this.globalTransactionId = globalTransactionId;
    this.branchQualifier = branchQualifier;
  }

  public int getFormatId() {
    return formatId;
  }

  public byte[] getGlobalTransactionId() {
    return globalTransactionId;
  }

  public byte[] getBranchQualifier() {
    return branchQualifier;
  }

  @Override
  public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + Arrays.hashCode(branchQualifier);
    result = prime * result + formatId;
    result = prime * result + Arrays.hashCode(globalTransactionId);
    return result;
  }

  public boolean equals(@Nullable Object o) {
    if (o == this) {
      // optimization for the common case.
      return true;
    }

    if (!(o instanceof Xid)) {
      return false;
    }

    Xid other = (Xid) o;
    return formatId == other.getFormatId()
        && Arrays.equals(globalTransactionId, other.getGlobalTransactionId())
        && Arrays.equals(branchQualifier, other.getBranchQualifier());
  }

  /**
   * This is for debugging purposes only.
   */
  public String toString() {
    return xidToString(this);
  }

  // --- Routines for converting xid to string and back.

  static String xidToString(Xid xid) {
    final byte[] globalTransactionId = xid.getGlobalTransactionId();
    final byte[] branchQualifier = xid.getBranchQualifier();
    final StringBuilder sb = new StringBuilder((int) (16 + globalTransactionId.length * 1.5 + branchQualifier.length * 1.5));
    sb.append(xid.getFormatId())
        .append('_')
        .append(Base64.getEncoder().encodeToString(globalTransactionId))
        .append('_')
        .append(Base64.getEncoder().encodeToString(branchQualifier));
    return sb.toString();
  }

  /**
   * @return recovered xid, or null if s does not represent a valid xid encoded by the driver.
   */
  static @Nullable Xid stringToXid(String s) {
    final int a = s.indexOf('_');
    final int b = s.lastIndexOf('_');

    if (a == b) {
      // this also catches the case a == b == -1.
      return null;
    }

    try {
      int formatId = Integer.parseInt(s.substring(0, a));
      //mime decoder is more forgiving to extraneous characters by ignoring them
      byte[] globalTransactionId = Base64.getMimeDecoder().decode(s.substring(a + 1, b));
      byte[] branchQualifier = Base64.getMimeDecoder().decode(s.substring(b + 1));
      return new RecoveredXid(formatId, globalTransactionId, branchQualifier);
    } catch (Exception ex) {
      final LogRecord logRecord = new LogRecord(Level.FINE, "XID String is invalid: [{0}]");
      logRecord.setParameters(new Object[] {s});
      logRecord.setThrown(ex);
      Logger.getLogger(RecoveredXid.class.getName()).log(logRecord);
      // Doesn't seem to be an xid generated by this driver.
      return null;
    }
  }
}
